Skip to content

perf: eliminate hot-path filesystem and regex costs in handler/routing layer#665

Merged
lmajano merged 3 commits into
developmentfrom
claude/hotpath-caching-optimizations
Jun 20, 2026
Merged

perf: eliminate hot-path filesystem and regex costs in handler/routing layer#665
lmajano merged 3 commits into
developmentfrom
claude/hotpath-caching-optimizations

Conversation

@lmajano

@lmajano lmajano commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to the Renderer discovery caching PR. This pass eliminates four confirmed hot-path costs in HandlerService, RoutingService, and Router — all on the per-request critical path with no API surface changes.

Issue 1 — HandlerService: cache ImplicitViews setting (high impact)

onConfigurationLoad() already caches every framework setting into variables.* but was missing ImplicitViews. The getHandler() method was calling controller.getSetting("ImplicitViews") on every handler dispatch where the action does not exist in the handler CFC.

Fix: Add variables.implicitViews = variables.controller.getSetting("ImplicitViews") to onConfigurationLoad() and replace the per-request call in getHandler().

Issue 2 — HandlerService: eliminate redundant fileExists(expandPath()) in isViewDispatch() (high impact)

isViewDispatch() was calling fileExists(expandPath(targetView)) after locateView() / locateModuleView() had already verified file existence. Those methods encode existence in their return value: a path with a .cfm/.bxm extension means the file was found; a bare name without extension means not found.

Fix: Replace the filesystem call with an extension check — zero I/O:

if ( right( targetView, 4 ) == ".cfm" || right( targetView, 4 ) == ".bxm" ) {

This also fixes a latent bug where locateModuleView() appends .bxm for BoxLang views but the old code only checked for .cfm.

Issue 3 — RoutingService: use cached canDebug in findRoute() (medium impact)

findRoute() cached getLogger().canDebug() into a local canDebug variable at the top of the method, but two downstream branches re-called getLogger().canDebug() (and getLogger()) instead of using the already-cached value.

Fix: Replace both stray getLogger().canDebug() calls with canDebug.

Issue 4 — Router/RoutingService: pre-parse route response placeholders at registration (medium impact)

renderResponse() ran reMatchNoCase("{[^{]+?}", aRoute.response) and reReplaceNoCase(...) on every request for routes with a string response value. Route definitions are immutable after registration, so the placeholder list never changes.

Fix (two-part):

  • Router.cfc addRoute() — pre-parse {token} placeholders into route.responsePlaceholders = [{token, key}, ...] at registration time.
  • RoutingService.cfc renderResponse() — iterate the pre-parsed list; no regex at render time.

This also fixes a latent bug in the original loop where each iteration replaced in aRoute.response rather than the accumulated theResponse, so only the last placeholder's substitution survived when a response contained multiple tokens.

Test Coverage

  • HandlerServiceTest.cfc — 3 new specs in a "Hot-path caching optimizations" describe block:
    • variables.implicitViews is a boolean cached after onConfigurationLoad()
    • isViewDispatch returns true for a view detected by extension
    • isViewDispatch returns false for a non-existent view
  • RouterTest.cfc — 5 new specs in a "Response placeholder pre-parsing" describe block:
    • Single placeholder parsed correctly
    • Multiple placeholders all parsed
    • String response with no placeholders produces empty array
    • Closure response produces empty responsePlaceholders
    • Route with no response produces empty responsePlaceholders

Files Changed

File Change
system/web/services/HandlerService.cfc Issues 1 & 2
system/web/services/RoutingService.cfc Issues 3 & 4b
system/web/routing/Router.cfc Issue 4a
tests/specs/web/services/HandlerServiceTest.cfc New hot-path specs
tests/specs/web/routing/RouterTest.cfc New placeholder specs

Generated by Claude Code

…g layer

Four targeted optimizations on the per-request execution path:

1. HandlerService: cache ImplicitViews setting
   `getSetting("ImplicitViews")` was called on every handler dispatch where
   the requested action was missing. It is now cached to variables scope in
   onConfigurationLoad() alongside every other already-cached setting.

2. HandlerService.isViewDispatch: drop redundant fileExists(expandPath())
   locateView/locateModuleView already verify file existence and return the
   path with a .cfm/.bxm extension only when the file is confirmed present.
   The follow-up fileExists() call was re-doing answered work. Replaced with
   a cheap string suffix check — zero filesystem I/O.

3. RoutingService.findRoute: use cached canDebug local variable consistently
   canDebug was already stored in a local variable at the top of findRoute()
   but three spots inside the same method re-called getLogger().canDebug()
   instead of using it. All three now use the cached local variable.

4. Router/RoutingService: pre-parse response string placeholders at registration
   renderResponse() was running reMatchNoCase + reReplaceNoCase on the route's
   static response string on every request. Tokens are now parsed once in
   addRoute() into a responsePlaceholders array; renderResponse() iterates the
   pre-parsed list with no regex at runtime. Also fixes a latent bug where
   multiple placeholders were not all applied (original always replaced into
   the original string rather than the accumulated result).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUinjJLvJod1u9s2e9T6zc
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 2b1a333. ± Comparison against base commit d6ccd51.

♻️ This comment has been updated with latest results.

claude added 2 commits June 20, 2026 08:44
… for $getProperty

- Router.addRoute() builds thisRoute from arguments struct, not
  initRouteDefinition(), so responsePlaceholders was only added for
  string responses. Add else branch to set [] for closure/empty cases.
- HandlerServiceTest: getHandlerService() returns the real service object;
  call prepareMock() before using $getProperty to access variables scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUinjJLvJod1u9s2e9T6zc
@lmajano lmajano merged commit 3d4cf97 into development Jun 20, 2026
26 of 28 checks passed
@lmajano lmajano deleted the claude/hotpath-caching-optimizations branch June 20, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants